import tkinter as tk
from tkinter import ttk, messagebox
import ctypes
import os
import sys
import time
from PIL import Image, ImageTk

class DirectIdCardReaderApp:
    """华视VCR-100UC身份证阅读器直接读取应用"""
    
    def __init__(self, root):
        self.root = root
        self.root.title("华视VCR-100UC身份证直读程序")
        self.root.geometry("700x500")
        self.ter_dll = None
        self.connected_port = None
        
        # 加载DLL
        self.load_dll()
        
        # 设置UI
        self.setup_ui()
        
    def load_dll(self):
        """加载DLL文件"""
        # 所有可能的DLL路径
        possible_paths = [
            "Termb.dll",  # 当前目录
            os.path.join(os.path.dirname(os.path.abspath(__file__)), "Termb.dll"),  # 脚本目录
            os.path.join(os.path.dirname(os.path.abspath(__file__)), "CVR100U", "Termb.dll"),  # 子目录
            "C:\\Termb.dll",  # C盘根目录
            os.path.join(os.environ.get('WINDIR', 'C:\\Windows'), "System32", "Termb.dll")  # 系统目录
        ]
        
        # 输出日志
        self.log("正在加载DLL文件...")
        self.log(f"当前工作目录: {os.getcwd()}")
        self.log(f"Python版本: {sys.version}")
        self.log(f"系统平台: {sys.platform}")
        
        # 尝试加载DLL
        for path in possible_paths:
            if os.path.exists(path):
                try:
                    self.log(f"尝试加载: {path}")
                    self.ter_dll = ctypes.windll.LoadLibrary(path)
                    self.log(f"成功加载DLL: {path}")
                    
                    # 检查相关文件
                    dll_dir = os.path.dirname(path)
                    self.check_related_files(dll_dir)
                    
                    return True
                except Exception as e:
                    self.log(f"加载 {path} 失败: {str(e)}")
        
        self.log("错误: 无法加载Termb.dll")
        messagebox.showerror("错误", "无法加载Termb.dll文件，请确保文件存在且有权限访问")
        return False
    
    def check_related_files(self, dll_dir):
        """检查相关文件"""
        self.log("=== 检查相关文件 ===")
        related_files = ["sdtapi.dll", "WltRS.dll", "DLL_File.dll"]
        for file in related_files:
            path = os.path.join(dll_dir, file)
            if os.path.exists(path):
                self.log(f"找到 {file}: {path}")
            else:
                self.log(f"警告: 未找到 {file}")
        
        # 检查授权文件
        license_path = "C:\\termb.lic"
        if os.path.exists(license_path):
            self.log(f"找到授权文件: {license_path}")
        else:
            self.log(f"警告: 未找到授权文件 {license_path}")
            
            # 尝试从项目目录复制
            src_license = os.path.join(dll_dir, "license.dat")
            if os.path.exists(src_license):
                try:
                    import shutil
                    shutil.copy(src_license, license_path)
                    self.log(f"已复制授权文件 {src_license} 到 {license_path}")
                except Exception as e:
                    self.log(f"无法复制授权文件: {str(e)}")
    
    def setup_ui(self):
        """设置用户界面"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 左侧信息区域
        info_frame = ttk.LabelFrame(main_frame, text="身份证信息", padding=10)
        info_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 右侧控制区域
        control_frame = ttk.Frame(main_frame, padding=10)
        control_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=5, pady=5)
        
        # 创建信息标签和值
        self.info_labels = {}
        fields = [
            "姓名", "性别", "民族", "出生日期", "地址", 
            "身份证号", "签发机关", "有效期起始", "有效期截止"
        ]
        
        for i, field in enumerate(fields):
            # 标签框架
            field_frame = ttk.Frame(info_frame)
            field_frame.pack(fill=tk.X, pady=5)
            
            # 字段名称
            label = ttk.Label(field_frame, text=f"{field}:", width=10, anchor=tk.W)
            label.pack(side=tk.LEFT)
            
            # 字段值
            value = ttk.Label(field_frame, text="", relief=tk.SUNKEN, anchor=tk.W)
            value.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
            
            self.info_labels[field] = value
        
        # 控制按钮
        read_btn = ttk.Button(control_frame, text="读取身份证", command=self.read_id_card)
        read_btn.pack(fill=tk.X, pady=5)
        
        clear_btn = ttk.Button(control_frame, text="清空数据", command=self.clear_data)
        clear_btn.pack(fill=tk.X, pady=5)
        
        test_btn = ttk.Button(control_frame, text="测试数据", command=self.use_test_data)
        test_btn.pack(fill=tk.X, pady=5)
        
        show_file_btn = ttk.Button(control_frame, text="查找文件", command=self.find_and_display_files)
        show_file_btn.pack(fill=tk.X, pady=5)
        
        # 日志区域
        log_frame = ttk.LabelFrame(self.root, text="操作日志", padding=5)
        log_frame.pack(fill=tk.X, padx=10, pady=5, side=tk.BOTTOM)
        
        # 添加滚动条
        log_scroll = ttk.Scrollbar(log_frame)
        log_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        
        self.log_text = tk.Text(log_frame, height=10, wrap=tk.WORD, yscrollcommand=log_scroll.set)
        self.log_text.pack(fill=tk.BOTH, expand=True)
        log_scroll.config(command=self.log_text.yview)
        
        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
    
    def log(self, message, end_line=True):
        """添加日志"""
        # 在日志框中添加消息
        timestamp = time.strftime("%H:%M:%S")
        
        if hasattr(self, 'log_text'):
            if end_line:
                self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
            else:
                self.log_text.insert(tk.END, f"[{timestamp}] {message}")
            self.log_text.see(tk.END)  # 滚动到最新消息
            self.root.update_idletasks()  # 确保UI更新
        
        # 同时输出到控制台
        if end_line:
            print(f"[{timestamp}] {message}")
        else:
            print(f"[{timestamp}] {message}", end="")
    
    def read_id_card(self):
        """读取身份证信息"""
        if not self.ter_dll:
            self.log("错误: DLL未加载")
            return
            
        self.status_var.set("正在读取身份证...")
        self.log("开始读取身份证...")
        
        # 清空原有数据
        self.clear_data(silent=True)
        
        # 尝试连接设备 - 优先尝试端口1001（根据命令行程序的成功结果）
        connected = False
        priority_ports = [1001] + [p for p in range(1002, 1017)]
        
        for port in priority_ports:
            try:
                self.log(f"尝试连接端口: {port}... ", end_line=False)
                result = self.ter_dll.CVR_InitComm(port)
                if result == 1:
                    self.log(f"成功!")
                    connected = True
                    self.connected_port = port
                    break
                else:
                    self.log(f"失败 (错误码: {result})")
            except Exception as e:
                self.log(f"异常: {str(e)}")
        
        if not connected:
            self.log("连接读卡器失败，请确保设备已正确连接")
            self.status_var.set("连接失败")
            messagebox.showerror("错误", "连接读卡器失败，请确保设备已正确连接")
            return
        
        try:
            # 等待设备稳定
            time.sleep(0.5)
            
            # 认证卡片
            self.log("=== 尝试认证卡片 ===")
            self.log("请将身份证放在读卡区...")
            self.log("尝试标准认证... ", end_line=False)
            auth_result = self.ter_dll.CVR_Authenticate()
            
            if auth_result == 1:
                self.log("成功!")
            else:
                self.log(f"失败 (错误码: {auth_result})")
                # 立即尝试无判断认证
                time.sleep(0.5)
                self.log("尝试无判断认证... ", end_line=False)
                try:
                    auth_result = self.ter_dll.CVR_AuthenticateForNoJudge()
                    if auth_result == 1:
                        self.log("成功!")
                    else:
                        self.log(f"失败 (错误码: {auth_result})")
                except Exception as e:
                    self.log(f"无判断认证异常: {str(e)}")
                    auth_result = -1
            
            if auth_result != 1:
                self.ter_dll.CVR_CloseComm()
                self.log(f"卡认证失败，请确保身份证放置正确")
                self.status_var.set("卡认证失败")
                messagebox.showerror("错误", "卡认证失败，请确保身份证已正确放置")
                return
            
            # 读取内容
            self.log("=== 尝试读取身份证内容 ===")
            self.log("尝试CVR_Read_Content... ", end_line=False)
            read_result = self.ter_dll.CVR_Read_Content(1)
            
            if read_result == 1:
                self.log("成功!")
            else:
                self.log(f"失败 (错误码: {read_result})，尝试CVR_Read_FPContent")
                time.sleep(0.5)
                self.log("尝试CVR_Read_FPContent... ", end_line=False)
                read_result = self.ter_dll.CVR_Read_FPContent()
                if read_result == 1:
                    self.log("成功!")
                else:
                    self.log(f"失败 (错误码: {read_result})")
            
            if read_result != 1:
                self.ter_dll.CVR_CloseComm()
                self.log(f"读取身份证失败，错误码: {read_result}")
                self.status_var.set(f"读取失败，错误码: {read_result}")
                messagebox.showerror("错误", f"读取身份证失败，错误码: {read_result}")
                return
            
            # 读取成功，从内存获取数据
            self.log("=== 身份证信息 (直接从内存获取) ===")
            info = self.get_id_card_data()
            
            if info:
                # 更新界面
                for field, value in info.items():
                    if field in self.info_labels:
                        self.info_labels[field].config(text=value)
                self.log("成功显示身份证信息")
                self.status_var.set("读取完成")
            else:
                self.log("未能获取身份证信息")
                self.status_var.set("未获取到数据")
                
            # 尝试显示生成的文件信息
            self.find_and_display_files(auto_mode=True)
        
        except Exception as e:
            self.log(f"读取过程发生异常: {str(e)}")
            self.status_var.set(f"读取异常")
            messagebox.showerror("错误", f"读取过程发生异常: {str(e)}")
        
        finally:
            # 关闭连接
            try:
                self.ter_dll.CVR_CloseComm()
                self.log("已关闭设备连接")
            except Exception as e:
                self.log(f"关闭连接异常: {str(e)}")
    
    def get_id_card_data(self):
        """直接从内存获取身份证信息"""
        if not self.ter_dll:
            return None
        
        try:
            # 创建缓冲区和长度变量
            buffer_size = 128
            info = {}
            
            # 获取姓名
            try:
                name_buffer = ctypes.create_string_buffer(buffer_size)
                name_len = ctypes.c_int(buffer_size)
                self.log("尝试获取姓名... ", end_line=False)
                if self.ter_dll.GetPeopleName(name_buffer, ctypes.byref(name_len)) == 1:
                    info["姓名"] = name_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['姓名']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取姓名异常: {e}")
            
            # 获取性别
            try:
                sex_buffer = ctypes.create_string_buffer(buffer_size)
                sex_len = ctypes.c_int(buffer_size)
                self.log("尝试获取性别... ", end_line=False)
                if self.ter_dll.GetPeopleSex(sex_buffer, ctypes.byref(sex_len)) == 1:
                    info["性别"] = sex_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['性别']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取性别异常: {e}")
            
            # 获取民族
            try:
                nation_buffer = ctypes.create_string_buffer(buffer_size)
                nation_len = ctypes.c_int(buffer_size)
                self.log("尝试获取民族... ", end_line=False)
                if self.ter_dll.GetPeopleNation(nation_buffer, ctypes.byref(nation_len)) == 1:
                    info["民族"] = nation_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['民族']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取民族异常: {e}")
            
            # 获取出生日期
            try:
                birthday_buffer = ctypes.create_string_buffer(buffer_size)
                birthday_len = ctypes.c_int(buffer_size)
                self.log("尝试获取出生日期... ", end_line=False)
                if self.ter_dll.GetPeopleBirthday(birthday_buffer, ctypes.byref(birthday_len)) == 1:
                    info["出生日期"] = birthday_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['出生日期']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取出生日期异常: {e}")
            
            # 获取地址
            try:
                address_buffer = ctypes.create_string_buffer(buffer_size)
                address_len = ctypes.c_int(buffer_size)
                self.log("尝试获取地址... ", end_line=False)
                if self.ter_dll.GetPeopleAddress(address_buffer, ctypes.byref(address_len)) == 1:
                    info["地址"] = address_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['地址']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取地址异常: {e}")
            
            # 获取身份证号
            try:
                id_buffer = ctypes.create_string_buffer(buffer_size)
                id_len = ctypes.c_int(buffer_size)
                self.log("尝试获取身份证号... ", end_line=False)
                if self.ter_dll.GetPeopleIDCode(id_buffer, ctypes.byref(id_len)) == 1:
                    info["身份证号"] = id_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['身份证号']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取身份证号异常: {e}")
            
            # 获取签发机关
            try:
                issue_buffer = ctypes.create_string_buffer(buffer_size)
                issue_len = ctypes.c_int(buffer_size)
                self.log("尝试获取签发机关... ", end_line=False)
                if self.ter_dll.GetDepartment(issue_buffer, ctypes.byref(issue_len)) == 1:
                    info["签发机关"] = issue_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['签发机关']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取签发机关异常: {e}")
            
            # 获取有效期起始
            try:
                start_buffer = ctypes.create_string_buffer(buffer_size)
                start_len = ctypes.c_int(buffer_size)
                self.log("尝试获取有效期起始... ", end_line=False)
                if self.ter_dll.GetStartDate(start_buffer, ctypes.byref(start_len)) == 1:
                    info["有效期起始"] = start_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['有效期起始']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取有效期起始异常: {e}")
            
            # 获取有效期截止
            try:
                end_buffer = ctypes.create_string_buffer(buffer_size)
                end_len = ctypes.c_int(buffer_size)
                self.log("尝试获取有效期截止... ", end_line=False)
                if self.ter_dll.GetEndDate(end_buffer, ctypes.byref(end_len)) == 1:
                    info["有效期截止"] = end_buffer.value.decode('gbk', errors='ignore')
                    self.log(f"成功: {info['有效期截止']}")
                else:
                    self.log("失败")
            except Exception as e:
                self.log(f"获取有效期截止异常: {e}")
            
            # 输出结果摘要
            self.log(f"获取到 {len(info)} 项身份证信息")
            
            return info
            
        except Exception as e:
            self.log(f"获取身份证信息失败: {str(e)}")
            return None
    
    def find_and_display_files(self, auto_mode=False):
        """查找和显示生成的文件"""
        if not auto_mode:
            self.log("=== 查找生成的文件 ===")
        
        # 可能的文件名
        possible_txt_names = ["wz.txt", "baseinfo.txt", "info.txt", "data.txt"]
        possible_img_names = ["zp.bmp", "photo.bmp", "pic.bmp", "zp.jpg", "zp.jpeg"]
        
        # 搜索目录
        search_dirs = [
            os.getcwd(),                                  # 当前目录
            os.path.dirname(os.path.abspath(__file__)),   # 脚本目录
            "C:\\",                                       # C盘根目录
            os.environ.get('TEMP', 'C:\\Temp'),           # 临时目录
            os.path.join(os.environ.get('LOCALAPPDATA', ''), "Temp", "chinaidcard"),  # 官方文档提到的默认路径
        ]
        
        # 查找文本文件
        txt_path = None
        for directory in search_dirs:
            if os.path.exists(directory):
                if not auto_mode:
                    self.log(f"搜索目录: {directory}")
                for filename in possible_txt_names:
                    path = os.path.join(directory, filename)
                    if os.path.exists(path):
                        self.log(f"找到文本文件: {path}")
                        txt_path = path
                        break
                if txt_path:
                    break
        
        # 查找图片文件
        img_path = None
        for directory in search_dirs:
            if os.path.exists(directory):
                for filename in possible_img_names:
                    path = os.path.join(directory, filename)
                    if os.path.exists(path):
                        self.log(f"找到图片文件: {path}")
                        img_path = path
                        break
                if img_path:
                    break
        
        # 显示文件内容
        if txt_path and not auto_mode:  # 自动模式下不显示额外的文本内容
            self.log("\n=== 身份证信息 (从文件读取) ===")
            try:
                self.display_text_file(txt_path)
            except Exception as e:
                self.log(f"读取文本文件失败: {str(e)}")
        elif not txt_path:
            self.log("未找到身份证信息文件")
            
        if img_path:
            self.log(f"图片文件路径: {img_path}")
            if not auto_mode:
                try:
                    # 尝试显示照片
                    img = Image.open(img_path)
                    photo_window = tk.Toplevel(self.root)
                    photo_window.title("身份证照片")
                    
                    # 调整大小，保持比例
                    max_size = (250, 300)
                    img.thumbnail(max_size, Image.Resampling.LANCZOS)
                    
                    photo = ImageTk.PhotoImage(img)
                    photo_label = ttk.Label(photo_window, image=photo)
                    photo_label.image = photo  # 保持引用
                    photo_label.pack(padx=10, pady=10)
                    
                    self.log("已显示身份证照片")
                except Exception as e:
                    self.log(f"显示照片失败: {str(e)}")
                    self.log("请使用图片查看器打开查看")
        else:
            self.log("未找到身份证照片文件")
            
        if not txt_path and not img_path and not auto_mode:
            self.log("警告: 未找到任何生成的文件")
            self.log("可能原因:")
            self.log("1. 读卡失败")
            self.log("2. 程序没有写入权限")
            self.log("3. 文件生成在其他位置")
    
    def display_text_file(self, file_path):
        """显示文本文件内容"""
        encodings = ['gbk', 'utf-8', 'gb2312']
        
        for encoding in encodings:
            try:
                with open(file_path, 'r', encoding=encoding) as f:
                    content = f.read()
                    self.log(f"(使用 {encoding} 编码读取)")
                    self.log("-" * 40)
                    self.log(content)
                    self.log("-" * 40)
                    return True
            except UnicodeDecodeError:
                continue
            except Exception as e:
                self.log(f"读取文件时发生错误: {str(e)}")
                return False
        
        self.log("无法以支持的编码读取文件")
        return False
    
    def clear_data(self, silent=False):
        """清空显示的数据"""
        for label in self.info_labels.values():
            label.config(text="")
        
        if not silent:
            self.log("数据已清空")
            self.status_var.set("已清空")
    
    def use_test_data(self):
        """使用测试数据"""
        test_info = {
            "姓名": "测试姓名",
            "性别": "男",
            "民族": "汉",
            "出生日期": "19800101",
            "地址": "测试地址信息",
            "身份证号": "123456789012345678",
            "签发机关": "测试公安局",
            "有效期起始": "20100101",
            "有效期截止": "20300101"
        }
        
        # 更新显示
        for field, value in test_info.items():
            if field in self.info_labels:
                self.info_labels[field].config(text=value)
        
        self.log("已加载测试数据")
        self.status_var.set("测试数据")

# 启动应用程序
if __name__ == "__main__":
    root = tk.Tk()
    app = DirectIdCardReaderApp(root)
    root.mainloop() 